#include "w25qxx.h"
#include "spi.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#if 1
#define __INFO(format,...) printf(""format"", ##__VA_ARGS__) 
#else
#define __INFO(format,...) 
#endif

#define cs_clr()    do{HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, GPIO_PIN_RESET);}while(0)
#define cs_set()    do{HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, GPIO_PIN_SET);}while(0)
#define W25QXX_SPI_TIMEOUT  1000
unsigned short w25qxx_spi_id(void)
{
    unsigned char buf[4];
    memset(buf, 0x00, sizeof(buf));
    buf[0] = READ_DEVICE_ID;

    cs_clr();
    HAL_SPI_Transmit(&hspi1, buf, 4, W25QXX_SPI_TIMEOUT);
    HAL_SPI_Receive(&hspi1, buf, 2, W25QXX_SPI_TIMEOUT);
    cs_set();
    return (uint16_t)((buf[0] << 8) | buf[1]);
}

int w25qxx_spi_init(void)
{
    unsigned short id;
    if (w25qxx_spi_reset()) 
    {
        __INFO("w25qxx_spi reset faild.\n");
        return -1;
    }
    HAL_Delay(100);
    id = w25qxx_spi_id();
    if ((id & 0xFF00) != 0xEF00) return -1;
    __INFO("SPI FLASH Device ID: %04X\n", id);
    return 0;
}

int   w25qxx_spi_reset(void)
{
    unsigned char instruction;
    instruction = ENABLE_RESET;
    cs_clr();
    HAL_SPI_Transmit(&hspi1, &instruction, 1, W25QXX_SPI_TIMEOUT);
    cs_set();
    HAL_Delay(1);
    instruction = RESET_DEVICE;
    cs_clr();
    HAL_SPI_Transmit(&hspi1, &instruction, 1, W25QXX_SPI_TIMEOUT);
    cs_set();
    HAL_Delay(1);
    return 0;
}

unsigned char w25qxx_spi_SR1(void)
{
    unsigned char instruction, value;
    instruction = READ_SR1;
    cs_clr();
    HAL_SPI_Transmit(&hspi1, &instruction, 1, W25QXX_SPI_TIMEOUT);
    HAL_SPI_Receive(&hspi1, &value, 1, W25QXX_SPI_TIMEOUT);
    cs_set();
    return value;
}

int w25qxx_spi_write_enable(void)
{
    unsigned char instruction;
    unsigned int start_tick;
    instruction = WRITE_ENABLE;
    cs_clr();
    HAL_SPI_Transmit(&hspi1, &instruction, 1, W25QXX_SPI_TIMEOUT);
    cs_set();
    start_tick = HAL_GetTick();
    while (1)
    {
        if ((HAL_GetTick() - start_tick) > W25QXX_SPI_TIMEOUT) return -1;
        if ((w25qxx_spi_SR1() & 0x02) == 0x02) break;
    }
    return 0;
}

int w25qxx_spi_wait_busy(void)
{
    unsigned int start_tick;
    start_tick = HAL_GetTick();
    while (1)
    {
        if ((HAL_GetTick() - start_tick) > W25QXX_SPI_TIMEOUT) return -1;
        if ((w25qxx_spi_SR1() & 0x01) == 0x00) break;
    }
    return 0;
}

int w25qxx_spi_erase(unsigned int address)
{
    unsigned char buf[4];
    buf[0] = SECTOR_ERASE;
    buf[1] = (unsigned char)(address >> 16);
    buf[2] = (unsigned char)(address >> 8);
    buf[3] = (unsigned char)(address);

    if (w25qxx_spi_write_enable()) 
    {
        __INFO("w25qxx_spi_write_enable faild.\n");
        return -1;
    }

    cs_clr();
    HAL_SPI_Transmit(&hspi1, buf, 4, W25QXX_SPI_TIMEOUT);
    cs_set();

    if (w25qxx_spi_wait_busy())
    {
        __INFO("w25qxx_spi_wait_busy faild.\n");
        return -1;
    }

    return 0;
}

int w25qxx_spi_read(unsigned int address, void *pbuf, int size)
{
    unsigned char buf[4];
    buf[0] = READ_DATA;
    buf[1] = (unsigned char)(address >> 16);
    buf[2] = (unsigned char)(address >> 8);
    buf[3] = (unsigned char)(address);

    cs_clr();
    HAL_SPI_Transmit(&hspi1, buf, 4, W25QXX_SPI_TIMEOUT);
    HAL_SPI_Receive(&hspi1, (unsigned char *)pbuf, size, W25QXX_SPI_TIMEOUT);
    cs_set();
    return size;
}

int w25qxx_spi_page_program(unsigned int address, void *pbuf, int size)
{
    unsigned char buf[4];
    buf[0] = PAGE_PROGRAM;
    buf[1] = (unsigned char)(address >> 16);
    buf[2] = (unsigned char)(address >> 8);
    buf[3] = (unsigned char)(address);

    if (w25qxx_spi_write_enable()) 
    {
        __INFO("w25qxx_spi_write_enable faild.\n");
        return -1;
    }

    cs_clr();
    HAL_SPI_Transmit(&hspi1, buf, 4, W25QXX_SPI_TIMEOUT);
    HAL_SPI_Receive(&hspi1, (unsigned char *)pbuf, size, W25QXX_SPI_TIMEOUT);
    cs_set();

    if (w25qxx_spi_wait_busy())
    {
        __INFO("w25qxx_spi_wait_busy faild.\n");
        return -1;
    }

    return size;
}

int w25qxx_spi_write_directly(unsigned int address, void *pbuf, int size)
{
    unsigned char *pdata;
    int write_size, remain;
    int ret;

    if (pbuf == NULL) return 0;
    pdata = (unsigned char *)pbuf;
    remain = size;

    while (remain > 0)
    {
        if (address % W25QXX_PAGE_SIZE)
            write_size = W25QXX_PAGE_SIZE - address % W25QXX_PAGE_SIZE;
        else 
            write_size = W25QXX_PAGE_SIZE;
        
        if(remain < write_size) 
            write_size = remain;

        ret = w25qxx_spi_page_program(address, pdata, write_size);
        if (ret != write_size) break;

        remain -= write_size;
        if (!remain) break;
        pdata += write_size;
        address += write_size;
    }
    return (int)(size - remain);
}

int w25qxx_spi_write(unsigned int address, void *pbuf, int size)
{
    unsigned char *pdata;
    int write_size, remain;
    unsigned char buffer[W25QXX_SECTOR_SIZE];
    int ret;

    if (pbuf == NULL) return 0;
    pdata = (unsigned char *)pbuf;
    remain = size;
    while (remain > 0)
    {
        if (address % W25QXX_SECTOR_SIZE)
            write_size = W25QXX_SECTOR_SIZE - address % W25QXX_SECTOR_SIZE;
        else
            write_size = W25QXX_SECTOR_SIZE;

        if (remain < write_size) write_size = remain;
        
        if (write_size != W25QXX_SECTOR_SIZE)
        {
            ret = w25qxx_spi_read(address - address % W25QXX_SECTOR_SIZE, buffer, W25QXX_SECTOR_SIZE);
            if (ret != W25QXX_SECTOR_SIZE) break;
        }
        memcpy(&buffer[address % W25QXX_SECTOR_SIZE], pdata, write_size);
        ret = w25qxx_spi_erase(address - address % W25QXX_SECTOR_SIZE);
        if (ret != 0) break;
        ret = w25qxx_spi_write_directly(address - address % W25QXX_SECTOR_SIZE, buffer, W25QXX_SECTOR_SIZE);
        if (ret != W25QXX_SECTOR_SIZE) break;

        remain -= write_size;
        if(!remain) break;
        pdata += write_size;
        address += write_size;
    }
    return (int)(size - remain);
}
